﻿//--------------------------------------------------------------------------
// 
//  Copyright (c) Chili Software.  All rights reserved. 
// 
//  File: Blob.cs
//
//  Description: Represents a binary large object.
// 
//--------------------------------------------------------------------------

using System;
using System.IO;
using System.Xml.Serialization;
using System.Text;

namespace Chili.Opf3
{
	/// <summary>Represents a BLOb (binary large object).</summary>
	/// <remarks>
	/// This class is used to save and load BLObs from and to the storage. BLObs are
	/// binary fields that contain an array of data (for example an image,
	/// sound, movies or something else). Usually the storage returns them directly as byte
	/// array which is not very convenient: therefore the framework provides the Blob
	/// class that encapsulates the byte array and provides a more convenient access to the data.
	/// </remarks>
	/// <example>
	///     The example shows how to use the Blob class in a persistent object.
	///     <code lang="CS">
	/// [Persistent("USER")]
	/// public class User
	/// {
	///     [Field("IMAGE", Mandatory = false)]
	///     public Blob Image
	///     {
	///         // ... code of the property.
	///     }
	///  
	///     [Field("ID", Mandatory = true, Identifier = true)]
	///     public ID&lt;string&gt; Id
	///     {
	///         // ... code of the property.
	///     }
	///  
	///     // ... Other code of the class.
	/// }
	///  
	/// // ... Other code.
	///  
	/// User user = new User();
	/// user.Image.Value = // Save something in it.
	///     </code>
	/// </example>
	[Serializable]
	public sealed class Blob : IUserDefinedType, ICloneable
	{
		private byte[] _bytes;

		/// <summary>Creates a new instance of the <see cref="Blob">Blob Class</see>.</summary>
		public Blob()
		{
		}

		/// <summary>Creates a new instance of the <see cref="Blob">Blob Class</see>.</summary>
		/// <param name="bytes">
		/// Bytes (representing the BLOb) managed by the <see cref="Blob">Blob</see>
		/// class.
		/// </param>
		public Blob(byte[] bytes)
		{
			this.Value = bytes;
		}

		#region Events

		/// <summary>
		/// Event fired before the value of the <see cref="Blob">Blob</see> is changed.
		/// </summary>
		public event EventHandler<EventArgs> ValueChanging;

		/// <summary>
		/// Event fired after the value of the <see cref="Blob">Blob</see> is changed.
		/// </summary>
		public event EventHandler<EventArgs> ValueChanged;

		/// <summary>
		/// Fires the corresponding event if somebody subscribed it.
		/// </summary>
		/// <param name="e">Instance of the EventArgs class.</param>
		private void OnValueChanging(EventArgs e)
		{
			if (ValueChanging != null)
				ValueChanging(this, e);
		}

		/// <summary>
		/// Fires the corresponding event if somebody subscribed it.
		/// </summary>
		/// <param name="e">Instance of the EventArgs class.</param>
		private void OnValueChanged(EventArgs e)
		{
			if (ValueChanged != null)
				ValueChanged(this, e);
		}

		#endregion

		/// <summary>Creates a <see cref="Blob">Blob</see> from a stream.</summary>
		/// <param name="stream">Stream that's data is used to populate the <see cref="Blob">Blob</see>.</param>
		public static Blob FromStream(Stream stream)
		{
			// Create the byte array from the stream.
			byte[] bytes = new byte[stream.Length];
			stream.Read(bytes, 0, (int)stream.Length);

			return new Blob(bytes);
		}

		#region IUserDefinedType Members

		/// <summary>
		/// Populates the type that implements the interface (a type that can't be directly saved or 
		/// loaded from the storage) with data from the storage.
		/// </summary>
		/// <param name="value">The simple type representation in the storage (for example an xml string).</param>
		void IUserDefinedType.FromSimpleDataType(object value)
		{
			this.Value = (byte[])value;
		}

		/// <summary>
		/// Converts the user-defined type to a type that can be persisted to the storage. You could
		/// return here for example an xml string or something else that can be saved to the storage.
		/// </summary>
		/// <returns>
		/// The IUserDefinedType converted to a simple type that is saved to the
		/// storage.
		/// </returns>
		object IUserDefinedType.ToSimpleDataType()
		{
			return this.Value;
		}

		/// <summary>
		/// Returns the type supported by the class implementing this interface. The supported type is the type
		/// that is returned when converting to a simple type and the type that is set when converting from the simple type.
		/// </summary>
		Type IUserDefinedType.SupportedType 
		{
			get { return typeof(byte[]); }
		}

		#endregion

		#region ICloneable Members

		/// <summary>Creates a new object that is a copy of the current instance.</summary>
		/// <returns>A new object that is a copy of this instance.</returns>
		public object Clone()
		{
			// Clone the blob object.
			return new Blob((byte[])this.Value.Clone());
		}

		#endregion

		/// <summary>Specifies the byte array managed by the <see cref="Blob">Blob</see>.</summary>
		/// <value>
		/// Returns the byte array managed by this instance of the
		/// <see cref="Blob">Blob</see> class.
		/// </value>
		public byte[] Value
		{
			get { return _bytes; }
			set 
			{
				// Fire the event that the value is about to change.
				OnValueChanging(EventArgs.Empty);

				_bytes = value;

				// Fire the event that the value has been changed recently.
				OnValueChanged(EventArgs.Empty);
			}
		}

		/// <summary>Returns the length of the data in the <see cref="Blob">Blob</see>.</summary>
		/// <value>Returns the length of the data in the <see cref="Blob">Blob</see>.</value>
		[XmlIgnore]
		public int Length
		{
			get
			{
				if (this.Value == null)
					return 0;
				return this.Value.Length;
			}
		}

		/// <summary>Converts the content of the <see cref="Blob">Blob</see> as string.</summary>
		/// <returns>Returns the content of the <see cref="Blob">Blob</see> as string.</returns>
		public override string ToString()
		{
			if (this.Value == null)
				return "";
			return Encoding.ASCII.GetString(this.Value);
		}

		/// <summary>
		/// Converts the content of the <see cref="Blob">Blob</see> to a memory stream. Null is returned
		/// if the <see cref="Blob">Blob</see> is empty.
		/// </summary>
		/// <returns>Returns the content of the <see cref="Blob">Blob</see> as memory stream.</returns>
		public MemoryStream ToMemoryStream()
		{
			if (this.Value == null)
				return null;
			return new MemoryStream(this.Value);
		}
	}
}
